package nl.hu.lcv.agents;
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.Behaviour;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.TickerBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.domain.introspection.AddedBehaviour;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.lang.acl.UnreadableException;
import jade.wrapper.AgentContainer;
import jade.wrapper.AgentController;
import jade.wrapper.ContainerController;
import jade.wrapper.StaleProxyException;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;

import nl.hu.lcv.blackboard.client.BlackboardClientUtils;
import nl.hu.lcv.blackboard.client.BlackboardMonitor;
import nl.hu.lcv.blackboard.data.PostItProtos.PostIt;
import nl.hu.lcv.blackboard.data.PostItProtos.PostItBox;
import nl.hu.lcv.blackboard.server.BlackboardManager;


// TODO: Auto-generated Javadoc
/**
 * The Class DirectorAgent. 
 */
public class DirectorAgent extends Agent
{
	
	/** The theoratical db stub. */
	private TheoraticalDatabase theoDb = null;
	
	/** The currentavailability db stub. */
	private CurrentAvailabilityDatabase currDb = null;
	
	/** The bb2 monitor. */
	private BlackboardMonitor bb2Monitor = null;
	
	/** The current time step. */
	private long currentTimeStep;	
	
	/**
	 * The Class TimeStepSynchronizeBehaviour. Informs agents about the current time step, which this behaviour also manages for now.
	 */
	private class TimeStepSynchronizeBehaviour extends TickerBehaviour
	{
		
		/**
		 * Instantiates a new time step synchronize behaviour.
		 *
		 * @param a the agent
		 * @param period the period
		 */
		public TimeStepSynchronizeBehaviour(Agent a, long period) {
			super(a, period);			
		}
				
		
		protected void onTick() 
		{
			try 
			{
				currentTimeStep++;
				ACLMessage msg = new ACLMessage(ACLMessage.INFORM);		
				
				DFAgentDescription template = new DFAgentDescription(); 
		        ServiceDescription sd = new ServiceDescription(); 
		        sd.setType("TimeReceiver");
		        template.addServices(sd); 	        
		        
		        
		        
		      //DFAgentDescription template2 = new DFAgentDescription(); 
		        
		  
		        try { 
		          DFAgentDescription[] result = DFService.search(myAgent, template ); 
		          //DFAgentDescription[] result2 = DFService.search(myAgent, template2); 
		          for (int i = 0; i < result.length; ++i) { 
		        	  //System.out.println(result[i].getName());
		        	  msg.addReceiver(result[i].getName()); 
		          } 
		          
		        } 
		        catch (FIPAException fe) { 
		          fe.printStackTrace(); 
		        }   
				
				msg.setLanguage("English");
				msg.setOntology("Boef-ontology");				
				msg.setContentObject(currentTimeStep);
				send(msg);
			} 
			catch (IOException e) 
			{					
				e.printStackTrace();
			}		
		}	
	}	
	
	/**
	 * The Class HandleNewProductBehaviour. 
	 * This behaviour handles calls by new products. 
	 */
	private class HandleNewProductBehaviour extends CyclicBehaviour
	{		
		//Wait for a new product		
		@Override
		public void action() 
		{				
			MessageTemplate mt = MessageTemplate.MatchPerformative(ACLMessage.CFP);
			ACLMessage msg = myAgent.receive(mt);
			if(msg != null)
			{
				
					// TODO read steps from blackboard, now steps are send in the message
									
					try {
						//decide theoretically possible to make with database
						Product newProduct = (Product)msg.getContentObject();
						boolean IsPossibleToMake = true;
						for(String a : newProduct.getSteps())
						{							
							//this should check for PLACE steps but 1 represents that in the db.
							if(!theoDb.getSteps().contains(1)){
								IsPossibleToMake = false;
								break;
							}
						}
						
					    //kudos is 1 for now, some variable factors should be included to alter this score (gridload)?
						if(IsPossibleToMake)
						{
							int kudos = 1;						
							ACLMessage reply = new ACLMessage(ACLMessage.PROPOSE);
							reply.setContent(""+kudos);
							reply.setInReplyTo(msg.getReplyWith());
							reply.setConversationId(msg.getConversationId());
							System.out.println("I agree");
							reply.addReceiver(msg.getSender());
							send(reply);
					    }
					    else
					    {
					    	ACLMessage reply = new ACLMessage(ACLMessage.REFUSE);
					    	reply.setInReplyTo(msg.getReplyWith());
							reply.setConversationId(msg.getConversationId());
							System.out.println("I refuse");
							reply.addReceiver(msg.getSender());
							send(reply);
					    }				
					} catch (UnreadableException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}			    
			}			
			else
				block();
		}
	}
	
	/**
	 * The Class InitiateProductBehaviour.
	 * This behaviour handles an accepted proposal by the productagent.
	 */
	private class InitiateProductBehaviour extends CyclicBehaviour
	{
		//Initiate the product at this grid and launch a behaviour for deciding which project manager handles the product
		
		public void action() 
		{
			MessageTemplate mt = MessageTemplate.MatchPerformative(ACLMessage.ACCEPT_PROPOSAL);
			ACLMessage msg = myAgent.receive(mt);				
			if(msg != null)
			{
				try {
					
					addBehaviour(new ProjectManagerSelectorBehaviour(msg.getContentObject()));
				} catch (UnreadableException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}	
			}
			else
				block();
		}			
	}
	
	/**
	 * The Class ProjectManagerSelectorBehaviour.
	 * This behaviour chooses an appropriate projectagent for the productagent, else creates a new one.
	 */
	private class ProjectManagerSelectorBehaviour extends Behaviour
	{
		
		/** The product. */
		private Product theProduct = null;
		
		/** The step. */
		private int step = 0;
		
		/** The number of project managers. */
		private int numberOfProjectManagers = 0;
		
		/** The best project agent. */
		private AID bestProjectAgent = null;
		
		/** The mt. */
		private MessageTemplate mt;
		
		
		/**
		 * Instantiates a new project manager selector behaviour.
		 *
		 * @param p the product
		 */
		public ProjectManagerSelectorBehaviour(Serializable p) {
			theProduct = (Product) p;
		}	
		
		/* (non-Javadoc)
		 * @see jade.core.behaviours.Behaviour#action()
		 */
		@Override
		public void action() {
			switch(step)
			{
				case(0):
					ACLMessage msg = new ACLMessage(ACLMessage.CFP);
					DFAgentDescription template = new DFAgentDescription(); 
			        ServiceDescription sd = new ServiceDescription(); 
			        sd.setType("Project");
			        template.addServices(sd); 
			        try { 
			          DFAgentDescription[] result = DFService.search(myAgent, template); 
			          for (int i = 0; i < result.length; ++i) { 
			        	  msg.addReceiver(result[i].getName()); 
			        	  numberOfProjectManagers++;
			          } 
			           	msg.setConversationId("ManagerSelector");
			          	msg.setContentObject(theProduct.getSteps());
			        } 
			 		catch (FIPAException fe) { 
				 		fe.printStackTrace(); 
		        	}
					catch (IOException e1) {					
						e1.printStackTrace();
			        }
					msg.setReplyWith("Manager"+System.currentTimeMillis());
			        mt = MessageTemplate.and(MessageTemplate.MatchConversationId("ManagerSelector"),
			        						 MessageTemplate.MatchInReplyTo(msg.getReplyWith()));
			        if(numberOfProjectManagers == 0)
			        	step = step+2;
			        else
			        {
			        	send(msg);
					   	step++;
			        }
					break;
				case(1):
					ACLMessage reply = myAgent.receive(mt);
				
					if(reply != null)
					{						
						//System.out.println("Check" + numberOfProjectManagers);
						
						numberOfProjectManagers--;
						if(reply.getPerformative() == ACLMessage.PROPOSE)
						{							
							//TODO Why is it the best? CONDITIONS?, should 
							bestProjectAgent = reply.getSender();															
						}			       
						if(numberOfProjectManagers == 0)
							step++;					
					}
					else
						block();
					
					block();
					break;
				case(2):
					if(bestProjectAgent == null)
					{
						System.out.println("ProjectAgent created");
						AgentContainer c = getContainerController();
				        try {
							AgentController a = c.createNewAgent(theProduct.getName()+ "P", "nl.hu.lcv.agents.ProjectAgent", new Object[]{theProduct, currDb,currentTimeStep, bb2Monitor });
							a.start();
				        } catch (StaleProxyException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}							
				        step++;
					}
					else
					{
						System.out.println("Here?????");
						ACLMessage ProjectAssignment = new ACLMessage(ACLMessage.ACCEPT_PROPOSAL);
						ProjectAssignment.addReceiver(bestProjectAgent);
						ProjectAssignment.setConversationId("Confirm");
						try {
							ProjectAssignment.setContentObject(theProduct);
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						
						send(ProjectAssignment);			
						step++;
									
					}			
				break;  
			}		
		}

		/* (non-Javadoc)
		 * @see jade.core.behaviours.Behaviour#done()
		 */
		@Override
		public boolean done() {
			return step == 3;
		}
		
		
	}
	
	
	
	/**
	 * The Class InitiateEquipletAgentBehaviour.
	 * This behaviour checks if a new equiplet has entered the system and creates an agent for it.
	 */
	private class InitiateEquipletAgentBehaviour extends TickerBehaviour
	{
			public InitiateEquipletAgentBehaviour(Agent a, long period) {
				super(a, period);			
			}
								
			protected void onTick() 
			{
				System.out.println(getLocalName());
				PostItBox ReadBox;
				ReadBox = PostItBox.newBuilder()
						.setIsWrite(false)
						.setReadOwner(getLocalName())
						.setZone("BB4") //sneaky forth blackboard, should find a better solution for it eventually
						.setCurrentTimeStep(currentTimeStep)
						.setFilter(PostItBox.Filter.newBuilder().setFiltername("PostItFilter"))
						.build();			 	
				 		//Look for new postits on the blackboard
				ReadBox = BlackboardClientUtils.readFromBlackboard(ReadBox);
				
				for(PostIt p : ReadBox.getPostItsList())
				{
					  System.out.println("FOUND!");
					  AgentContainer c = getContainerController();
					  try {
				            AgentController a = c.createNewAgent( p.getPayload(), "nl.hu.lcv.agents.EquipletAgent", new Object[]{currDb} );
				            a.start();
					  }
					  catch (Exception e){}		   
					  currDb.addEquiplet(p.getPayload());
				}		
			}		
	}
	
	
	
	public void setup()
	{	
		bb2Monitor = new BlackboardMonitor();
		theoDb = new TheoraticalDatabase();
		currDb = new CurrentAvailabilityDatabase(null);
		
		DFAgentDescription dfd = new DFAgentDescription(); 
 	    dfd.setName(getAID()); 
 	    ServiceDescription sd = new ServiceDescription(); 
 	    sd.setType("Director"); 
 	    sd.setName("Director-agent");
 	    dfd.addServices(sd); 
 	    try { 
 	      DFService.register(this, dfd); 
 	    } 
 	    catch (FIPAException fe) { 
 	      fe.printStackTrace();
 	    }
		
 	    addBehaviour(new InitiateEquipletAgentBehaviour(this, 5000));
		addBehaviour(new TimeStepSynchronizeBehaviour(this, 3000));
		addBehaviour(new HandleNewProductBehaviour());
		addBehaviour(new InitiateProductBehaviour());	
 	    
 	   
 	    
        /*AgentContainer c = getContainerController();
        try {
            AgentController a = c.createNewAgent( "B100", "nl.hu.lcv.agents.EquipletAgent", null );
            AgentController b = c.createNewAgent( "Harry", "nl.hu.lcv.agents.EquipletAgent", null );
            AgentController d = c.createNewAgent( "B1", "nl.hu.lcv.agents.EquipletAgent", null );
            AgentController e = c.createNewAgent( "B2", "nl.hu.lcv.agents.EquipletAgent", null );           
            a.start();
            b.start();
            d.start();
            e.start();           
            
             
            currDb.EquipletWrites("B100", new ArrayList<Integer>(Arrays.asList(new Integer[]{ 1,2,3,4,5,6,7,8,9,10})));
            currDb.EquipletWrites("Harry", new ArrayList<Integer>(Arrays.asList(new Integer[]{ 11,12,15,16,17,18,19,20})));
            currDb.EquipletWrites("B1", new ArrayList<Integer>(Arrays.asList(new Integer[]{ 6,7,8,9,10,11,12})));
            currDb.EquipletWrites("B2", new ArrayList<Integer>(Arrays.asList(new Integer[]{ 13,14,15})));
            
        }
        catch (Exception e){}*/
 	    	
	}	
}
